package model;

import java.util.Random;
import javafx.scene.paint.Color;
import engine.Cell;
import engine.LayerAgents;
import engine.LayerFluids;
import engine.Parameter;
import engine.Scheduler;
import engine.SchedulerListener;
import engine.Simulation;
import engine.StatemachineModel;

/**
 * Global properties about the model.
 *
 * @author Ricardo Cruz {@literal <ricardo.pdm.cruz@gmail.com>}
 * @since  2014-11-12
 */
public class Model implements SchedulerListener
{
	//** properties which apply to all models

	private static final Parameter<Integer> W =
		new Parameter<Integer>(-1, "global", "petri-width-um", 3000);
	private static final Parameter<Integer> H =
		new Parameter<Integer>(-1, "global", "petri-height-um", 2000);
	private static final Parameter<Integer> R =
		new Parameter<Integer>(1, "global", "containing-well-radius-um", 250);
	private static final Parameter<Integer> S =
		new Parameter<Integer>(1, "global", "containing-well-separation-um", 1000);
	private static final Parameter<Integer> A =
		new Parameter<Integer>(1, "global", "cells-containing-well-nbr", 1000);
	public static final Parameter<Long> M =
		new Parameter<Long>(1, "global", "molecules-grain-containing-well-nbr", 1200000l);
	private static final Parameter<Integer> B0 =
		new Parameter<Integer>(0, "global", "initial-bcell-number", 3000);
	private static final Parameter<Double> dT =
		new Parameter<Double>(-1, "global", "update-diffuse-time", (1./24)/60);

	private static int currentModel = 0;

	public static int getCellTypes() {
		return 2;
	}

	public static String getCellName(int type) {
		switch(type) {
		case 0: return "B cell";
		case 1: return "Motile cell";
		}
		return null;
	}

	public static Color getCellColor(int type) {
		switch(type) {
		case 0: return Color.GREEN;
		case 1: return Color.WHITE;
		}
		return null;
	}

	@SuppressWarnings("rawtypes")
	public static StatemachineModel getCellModel(int model) {
		switch(model) {
			case 0: return BCell.getModel();
			case 1: return MCell.getModel();
		}
		return null;
	}

	public static Cell thymusCreateCell(int type) {
		return new BCell(null);
	}

	//** model specific functions

	public static String getModelName(int model) {
		switch(model) {
			case 0: return "Guo 2007";
			case 1: return "Guo 2008";
		}
		return null;
	}

	public static int getNbrModels() {
		return 2;
	}

	public static String[] getModelLegend(int model) {
		switch(model) {
			case 0:
				return new String[] {"naive","activated","plasma","memory","death"};
		}
		return new String[]{"Cells"};
	}

	/**
	 * Petri dish width in micro-meters.
	 *
	 * <p>Should be a multiple of 4 (cell size) and 20 (molecular region).</p>
	 * @return Petri dish width (mm)
	 */
	public static int getPetriWidth() {
		return W.get().intValue();
	}

	/**
	 * Petri dish height in micro-meters.
	 *
	 * <p>Should be a multiple of 4 (cell size) and 20 (molecular region).</p>
	 * @return Petri dish height (mm)
	 */
	public static int getPetriHeight() {
		return H.get().intValue();
	}

	public static long getInitialCount(int model, int i) {
		switch(model) {
			case 0: if(i == 0) return B0.get();
		}
		return 0;
	}

	//** other stuff

	public static void setup(int model, LayerAgents agents, LayerFluids fluids) {
		new Model(model, agents, fluids);
	}

	public static int getCurrentModel() {
		return currentModel;
	}

	public static String description(int model) {
		switch(model) {
		case 0: return
			"The Petri dish blood sample is populated with 3000 B cells. At day "
			+ "6th, antigen virions are introduced and binding to B cells happens "
			+ "until day 10. From days 11-13, some B cells get activated. There are "
			+ "3 types of virions.";
		case 1: return
			"Demonstration of chemotaxis; motile cells consume ligand and move in "
			+ "its direction. (Ricardo: I had to remove Volume from the equations for"
			+ "the dynamics to be similar.)";
		}
		return null;
	}

	//** model stuff

	enum Event {
		BIND_VIRION,
		ACTIVATION,
		DIFFUSE_STEP,
	}

	/**
	 * Initializes models' behavior. It creates cells and molecules, and launches
	 * events that it then processes.
	 * @param model
	 * @param agents
	 * @param fluids
	 */
	private Model(int model, LayerAgents agents, LayerFluids fluids) {
		currentModel = model;
		switch(model) {
		case 0:
			for(int i = 0; i < Model.getCellTypes(); i++)
				for(int n = 0; n < Model.getInitialCount(model,i); n++)
					agents.add(Model.thymusCreateCell(i));
			Scheduler.post(6, this, Event.BIND_VIRION);
			Scheduler.post(11, this, Event.ACTIVATION);
			break;
		case 1:
			Random random = new Random();
			// cells-containing well
			for(int i = 0; i < A.get(); i++) {
				// disk point picking algorithm
				// http://mathworld.wolfram.com/DiskPointPicking.html
				final double x0 = (W.get()-S.get())/2-R.get();
				final double y0 = H.get()/2;
				double r = random.nextDouble();
				double theta = random.nextDouble()*2*Math.PI;
				double x = (Math.sqrt(r)*Math.cos(theta))*R.get()+x0;
				double y = (Math.sqrt(r)*Math.sin(theta))*R.get()+y0;
				agents.add(new MCell(x,y));
			}
			// chemoattractant-containing well
			for(int x = -R.get(); x <= R.get(); x += LayerFluids.dL.get())
				for(int y = -R.get(); y <= R.get(); y += LayerFluids.dL.get()) {
					final double x0 = (W.get()+S.get())/2+R.get();
					final double y0 = H.get()/2;
					if(x*x + y*y <= R.get()*R.get())
						fluids.secrete(x+x0, y+y0, M.get());
				}
			fluids.diffuse();
			// only use for this this model -- cause this is too slow
			Scheduler.post(dT.get(), this, Event.DIFFUSE_STEP);
			break;
		}
	}

	public void event(Object event) {
		Cell cell = Simulation.get().getAgentsLayer().getRandomCell();
		switch((Event)event) {
			case BIND_VIRION:
				cell.issue(BCell.MyEvent.BIND_VIRION, (int)(Math.random()%3));
				if(Scheduler.clock() < 10)
					Scheduler.post(0.001, this, Event.BIND_VIRION);
				break;
			case ACTIVATION:
				cell.issue(BCell.MyEvent.ACTIVATION, null);
				if(Scheduler.clock() < 13)
					Scheduler.post(0.3, this, Event.ACTIVATION);
				break;
			case DIFFUSE_STEP:
				Simulation.get().getFluidsLayer().diffuse();
				Simulation.get().getAgentsLayer().diffuse();
				Scheduler.post(dT.get(), this, Event.DIFFUSE_STEP);
				break;
		}
	}
}
